import UIKit
#if canImport(MJRefresh)
import MJRefresh
#endif
#if canImport(SnapKit)
import SnapKit
#endif
#if canImport(Aspect)
import Aspect
#endif

// Keys for associated objects
private var kCenterOffsetYKey: Void?
private var kEmptyViewKey: Void?

public extension UIScrollView {
#if canImport(MJRefresh)

  // MARK: - Refreshers

  func addHeaderRefresher(refreshBlock: @escaping () -> Void) {
    if let header = self.mj_header {
      header.refreshingBlock = refreshBlock
    } else {
      self.mj_header = MJRefreshNormalHeader(refreshingBlock: refreshBlock)
//          let header = MJRefreshLottieHeader(refreshingBlock: refreshBlock)
//          header.animationResources = [
//            .idle: "lottie_anim8",
//            .pulling: "lottie_anim6",
//            .refreshing: "lottie_anim7"
//          ]
//          self.mj_header = header
    }
  }

  /// Adds a footer refresher (load more) to the scroll view.
  /// - Parameter refreshBlock: The block to be executed when the footer is refreshed.
  func addFooterRefresher(refreshBlock: @escaping () -> Void) {
    if let footer = self.mj_footer {
      footer.refreshingBlock = refreshBlock
    } else {
      self.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: refreshBlock)
      self.mj_footer?.isHidden = true // Initially hide the footer
    }
  }

  /// Begins refreshing the header.
  func beginRefreshing() {
    self.beginRefreshing(isHeader: true)
  }

  /// Begins refreshing either the header or the footer.
  /// - Parameter isHeader: A boolean indicating whether to refresh the header (true) or footer (false).
  func beginRefreshing(isHeader: Bool) {
    if isHeader {
      self.mj_header?.beginRefreshing()
    } else {
      self.mj_footer?.beginRefreshing()
    }
  }

  /// Ends refreshing for both header and footer.
  /// - Parameter noData: If true, the footer will be hidden, indicating no more data.
  func endRefreshing(noData: Bool = true) {
    self.mj_header?.endRefreshing()
    self.mj_footer?.endRefreshing()
    self.mj_footer?.isHidden = noData
  }
#endif

#if canImport(SnapKit) && canImport(Aspect)

  // MARK: - Empty View

  /// The vertical offset for the empty view from the center.
  var centerOffsetY: CGFloat {
    get {
      return objc_getAssociatedObject(self, &kCenterOffsetYKey) as? CGFloat ?? 0.0
    }
    set {
      objc_setAssociatedObject(self, &kCenterOffsetYKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
      // Update empty view constraints if it exists
      self.emptyView?.snp.updateConstraints { make in
        make.centerY.equalToSuperview().offset(newValue)
      }
    }
  }

  /// The custom view to display when the scroll view's content is empty.
  var emptyView: UIView? {
    get {
      return objc_getAssociatedObject(self, &kEmptyViewKey) as? UIView
    }
    set {
      // Remove old empty view if it exists
      (objc_getAssociatedObject(self, &kEmptyViewKey) as? UIView)?.removeFromSuperview()

      objc_setAssociatedObject(self, &kEmptyViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

      if let newEmptyView = newValue {
        newEmptyView.isHidden = true // Initially hidden
        self.addSubview(newEmptyView)
        let size = newEmptyView.frame.size == .zero ?newEmptyView.intrinsicContentSize:newEmptyView.frame.size
        newEmptyView.snp.remakeConstraints { make in
          make.centerX.equalToSuperview()
          make.centerY.equalToSuperview().offset(self.centerOffsetY)
          make.size.equalTo(size)
        }
        // Call refreshEmptyViewState to set initial visibility
        self.refreshEmptyViewState()
      }

      // Aspect hooking for automatic empty view refresh
      let reloadSel = NSSelectorFromString("reloadData")
      let endUpdatesSel = NSSelectorFromString("endUpdates") // For UITableView (batch updates)

      // Hook reloadData for UITableView and UICollectionView
      if self.responds(to: reloadSel) {
        hook(reloadSel, position: .after, usingBlock: { [weak self] _ in
          self?.refreshEmptyViewState()
        } as AspectBlock)
      }

      // Hook endUpdates for UITableView
      if self.responds(to: endUpdatesSel) {
        hook(endUpdatesSel, position: .after, usingBlock: { [weak self] _ in
          self?.refreshEmptyViewState()
        } as AspectBlock)
      }
    }
  }

  /// Sets the empty view with a specified Y-offset.
  /// - Parameters:
  ///   - view: The UIView to be used as the empty view. Pass nil to remove the empty view.
  ///   - yOffset: The vertical offset for the empty view.
  func setEmptyView(_ view: UIView?, yOffset: CGFloat = 0) {
    self.centerOffsetY = yOffset
    self.emptyView = view
  }

  // MARK: - Private Helper Methods

  /// Calculates the total number of items in a UITableView or UICollectionView.
  private var itemsCount: Int {
    if let tableView = self as? UITableView {
      var count = 0
      for i in 0..<tableView.numberOfSections {
        count += tableView.numberOfRows(inSection: i)
      }
      return count
    }
    if let collectionView = self as? UICollectionView {
      var count = 0
      for i in 0..<collectionView.numberOfSections {
        count += collectionView.numberOfItems(inSection: i)
      }
      return count
    }
    return 0
  }

  /// Refreshes the visibility of the empty view based on the number of items.
  /// Call this method after updating your data source and calling `reloadData()`
  /// or `endUpdates()` on a UITableView or UICollectionView.
  @objc func refreshEmptyViewState() {
    self.emptyView?.isHidden = self.itemsCount > 0
  }
#endif
}
